// -*- swift -*-
// RUN: rm -rf %t ; mkdir -p %t
// RUN: %S/../../utils/gyb %s -o %t/Slice.swift
// RUN: %S/../../utils/line-directive %t/Slice.swift -- %target-build-swift %t/Slice.swift -o %t/a.out
// RUN: %S/../../utils/line-directive %t/Slice.swift -- %target-run %t/a.out
// REQUIRES: executable_test

import StdlibUnittest
import StdlibCollectionUnittest
import SwiftPrivate


var SliceTests = TestSuite("Collection")

//===----------------------------------------------------------------------===//
// Slice<Base>
//===----------------------------------------------------------------------===//

SliceTests.test("Slice/AssociatedTypes") {
%for traversal in ['Forward', 'Bidirectional', 'RandomAccess']:
  do {
    typealias Base = Minimal${traversal}Collection<OpaqueValue<Int>>
    typealias ${traversal}Slice = Slice<Base>
    expectSliceType(${traversal}Slice.self)
    expectCollectionAssociatedTypes(
      collectionType: ${traversal}Slice.self,
      iteratorType: IndexingIterator<${traversal}Slice>.self,
      subSequenceType: ${traversal}Slice.self,
      indexType: Minimal${traversal}Index.self)
  }
%end

  func checkStaticTypealiases<Base : Collection>(_: Base) {
    expectEqualType(Base.Index.self, Slice<Base>.Index.self)
  }
}

SliceTests.test("Slice/init(_base:bounds:)") {
  for test in subscriptRangeTests {
    let base = MinimalForwardCollection(elements: test.collection)
    var result = Slice(_base: base, bounds: test.boundsIn(base))
    expectType(
      Slice<MinimalForwardCollection<OpaqueValue<Int>>>.self,
      &result)

    checkForwardCollection(
      test.expected,
      result,
      stackTrace: SourceLocStack().with(test.loc))
      { $0.value == $1.value }
  }
}

SliceTests.test("Slice.{startIndex,endIndex}") {
  for test in subscriptRangeTests {
    let base = MinimalForwardCollection(elements: test.collection)
    let bounds = test.boundsIn(base)
    var slice = Slice(_base: base, bounds: bounds)
    expectType(
      Slice<MinimalForwardCollection<OpaqueValue<Int>>>.self,
      &slice)

    expectEqual(bounds.startIndex, slice.startIndex)
    expectEqual(bounds.endIndex, slice.endIndex)
  }
}

SliceTests.test("Slice.subscript(_: Index)") {
  for test in subscriptRangeTests {
    typealias Base = MinimalForwardCollection<OpaqueValue<Int>>
    Base.Index.trapOnRangeCheckFailure.value = false
    let base = Base(elements: test.collection)
    let bounds = test.boundsIn(base)
    var slice = Slice(_base: base, bounds: bounds)
    expectType(Slice<Base>.self, &slice)

    for i in bounds {
      var element = slice[i]
      expectType(OpaqueValue<Int>.self, &element)
      expectEqual(base[i].value, element.value)
    }

    for (i, index) in base.indices.enumerated() {
      if test.bounds.contains(i) {
        expectEqual(base[index].value, slice[index].value)
      } else {
        // `Slice` disallows out-of-bounds indices when the underlying index
        // type can perform a range check.
        expectFailure {
          _blackHole(slice[index])
        }
      }
    }
  }
}

SliceTests.test("Slice.subscript(_: Range<Index>)") {
  for test in subscriptRangeTests {
    typealias Base = MinimalForwardCollection<OpaqueValue<Int>>
    Base.Index.trapOnRangeCheckFailure.value = false
    let base = Base(elements: test.collection)
    var slice = Slice(_base: base, bounds: base.indices)
    expectType(Slice<Base>.self, &slice)

    var result = slice[test.boundsIn(slice)]
    expectType(Slice<Base>.self, &result)

    checkForwardCollection(test.expected, result) { $0.value == $1.value }

    if test.bounds == test.collection.indices {
      let reSliced = result[base.indices]
      checkForwardCollection(
        test.collection,
        reSliced,
        stackTrace: SourceLocStack().with(test.loc))
        { $0.value == $1.value }
    } else {
      // `Slice` disallows out-of-bounds slicing when the underlying index type
      // can perform a range check.
      expectFailure {
        _blackHole(result[base.indices])
      }
    }
  }
}


//===----------------------------------------------------------------------===//
// MutableSlice<Base>
//===----------------------------------------------------------------------===//

SliceTests.test("MutableSlice/AssociatedTypes") {
%for traversal in ['Forward', 'Bidirectional', 'RandomAccess']:
  do {
    typealias Base =
      Minimal${traversal}MutableCollection<OpaqueValue<Int>>
    typealias ${traversal}MutableSlice = MutableSlice<Base>
    expectMutableSliceType(${traversal}MutableSlice.self)
    expectCollectionAssociatedTypes(
      collectionType: ${traversal}MutableSlice.self,
      iteratorType: IndexingIterator<${traversal}MutableSlice>.self,
      subSequenceType: ${traversal}MutableSlice.self,
      indexType: Minimal${traversal}Index.self)
  }
%end

  func checkStaticTypealiases<
    Base : MutableCollection
  >(_: Base) {
    expectEqualType(Base.Index.self, MutableSlice<Base>.Index.self)
  }
}

/*
FIXME: uncomment this test when the following bug is fixed:

<rdar://problem/21935030> Recast Slice and MutableSlice in terms of Collection
and MutableCollection

extension MutableSlice {
  func _checkBaseSubSequenceElementIsElement() {
      Element.self,
      Iterator.Element.self)
    expectEqualType(
      Element.self,
      Iterator.Element.self,
      Base.Iterator.Element.self)
    expectEqualType(
      Element.self,
      Iterator.Element.self,
      Base.SubSequence.Iterator.Element.self)
  }
}
*/

SliceTests.test("MutableSlice/init(_base:bounds:)") {
  for test in subscriptRangeTests {
    let c = MinimalForwardMutableCollection(elements: test.collection)
    var result = MutableSlice(_base: c, bounds: test.boundsIn(c))
    expectType(
      MutableSlice<MinimalForwardMutableCollection<OpaqueValue<Int>>>.self,
      &result)

    checkForwardCollection(
      test.expected,
      result,
      stackTrace: SourceLocStack().with(test.loc))
      { $0.value == $1.value }
  }
}

SliceTests.test("MutableSlice.{startIndex,endIndex}") {
  for test in subscriptRangeTests {
    let c = MinimalForwardMutableCollection(elements: test.collection)
    let bounds = test.boundsIn(c)
    var slice = MutableSlice(_base: c, bounds: bounds)
    expectType(
      MutableSlice<MinimalForwardMutableCollection<OpaqueValue<Int>>>.self,
      &slice)

    expectEqual(bounds.startIndex, slice.startIndex)
    expectEqual(bounds.endIndex, slice.endIndex)
  }
}

SliceTests.test("MutableSlice.subscript(_: Index)/{get,set}") {
  for test in subscriptRangeTests {
    typealias Base = MinimalForwardMutableCollection<OpaqueValue<Int>>
    Base.Index.trapOnRangeCheckFailure.value = false
    let base = Base(elements: test.collection)
    let bounds = test.boundsIn(base)
    var slice = MutableSlice(_base: base, bounds: bounds)
    expectType(MutableSlice<Base>.self, &slice)

    for i in bounds {
      // Test getter.
      var element = slice[i]
      expectType(OpaqueValue<Int>.self, &element)
      expectEqual(base[i].value, element.value)
    }

    do {
      var sliceForSetter = slice
      for i in bounds {
        // Test setter.
        sliceForSetter[i].value += 1
        expectEqual(base[i].value + 1, sliceForSetter[i].value)
      }
    }

    var sliceForSetter = slice
    for (i, index) in base.indices.enumerated() {
      if test.bounds.contains(i) {
        // Test getter.
        expectEqual(base[index].value, slice[index].value)

        // Test setter.
        sliceForSetter[index].value += 1
        expectEqual(base[index].value + 1, sliceForSetter[index].value)
      } else {
        // `MutableSlice` disallows out-of-bounds indices when the underlying
        // index type can perform a range check.

        // Test getter.
        expectFailure {
          _blackHole(slice[index])
        }

        // Test setter.
        expectFailure {
          sliceForSetter[index].value += 1
        }
      }
    }

    // Check that none of the mutations above affected the original collection.
    expectEqualSequence(test.collection, base) { $0.value == $1.value }
  }
}

runAllTests()
